package com.code.question5.impl;

import com.alibaba.fastjson.JSON;
import com.code.question5.base.MessageType;
import com.code.question5.model.SendMessage;
import com.code.question5.model.User;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.rmi.server.ExportException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 服务端服务类
 */
public class GroupTalkServiceImpl implements Runnable {
    private Socket socket;
    private User user;
    private boolean isLogin = false;
    private boolean isOnline = true;
    //在线客户端
    public static Map<String, Socket> onlineClient = new HashMap<>();
    public static Map<String, User> onlineUser = new HashMap<>();

    public GroupTalkServiceImpl(Socket s, boolean isLogin) {
        this.socket = s;
        this.isLogin = isLogin;
    }

    @Override
    public void run() {
        InputStream reader = null;
        PrintStream print = null;
        File file = new File("");
        try {
            print = new PrintStream(socket.getOutputStream());
            reader = socket.getInputStream();
            String clientAddress = socket.getInetAddress() + ":" + socket.getPort();
            while (isOnline) {
                StringBuilder sb = new StringBuilder();
                DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                LocalDateTime time = LocalDateTime.now();
                byte[] readByte = new byte[1024];
                //客户端强制退出时，read监听会出socket 连接异常，这时候直接将用户置为下线即可
                try {
                    reader.read(readByte);
                }catch (Exception ex){
                    onlineClient.remove(clientAddress);
                    isOnline = false;
                    sb.append(dtf.format(time) + "----" + "用户：" + this.user.getName() + "下线");
                    System.out.println("用户：" + this.user.getName() + "下线");
                    forwardMessageOthers(sb.toString(), clientAddress);
                    writeTalkFileRepository(sb.toString() + "\r\n");
                    break;
                }
                String readStr = new String(readByte).trim();
                //第一次客户端连接是，会初始化线程与用户对应关系，并提示其他客户端用户上线信息
                if (isLogin) {
                    isLogin = false;
                    this.user = JSON.parseObject(readStr, User.class);
                    onlineUser.put(clientAddress, this.user);
                    onlineClient.put(clientAddress, socket);
                    sb.append(dtf.format(time) + "----" + "用户：" + this.user.getName() + "上线了。。。");
                    System.out.println(sb.toString());
                } else {
                    //其余时间即应用时间，对客户端发送的命令进行序列化，判断用户指令类型，是文件还是文本
                    SendMessage sm = JSON.parseObject(readStr, SendMessage.class);
                    if (sm != null && sm.getMessageType().equals(MessageType.TEXT)) {
                        //当接收到的是文本，拼接对应的用户和时间进行分发推送
                        sb.append(dtf.format(time) + "----" + this.user.getName() + ":" + sm.getContent());
                    } else if (sm != null && sm.getMessageType().equals(MessageType.FILE)) {
                        //当接收到的类型是文件，则进行文件的接收下载到服务器的文件库中
                        long length = sm.getLength();//接收文件的大小
                        long downloadLen = 0;
                        byte[] filestream = new byte[20480];//初始化缓存区
                        int len = 0;
                        //获取项目所在地址的盘符，并找到仓库地址，不存在则创建，其实可以提取为通用方法，放到以后优化吧
                        String projectPath = file.getCanonicalPath();
                        String diskPath = projectPath.substring(0, projectPath.indexOf("\\") + 1);
                        File receivefile = new File(diskPath + "Repository\\" + sm.getName());
                        if (!receivefile.exists()) {
                            receivefile.createNewFile();
                        }
                        //生成接收上传文件的文件
                        FileOutputStream fileout = new FileOutputStream(receivefile);
                        //获取上传文件数据流
                        DataInputStream dis = new DataInputStream(socket.getInputStream());
                       //开始传输文件流信息
                        while ((len = dis.read(filestream, 0, filestream.length)) != -1) {
                            System.out.println("已下载" + len + "字节");
                            fileout.write(filestream, 0, len);
                            downloadLen += len;
                            fileout.flush();
                            //注意，因为read方法是一直监听状态的，所以循环在接收完毕时不会退出，而是会卡在那一直监听，所以需要通过已经接收到的字节长度与已知文件长度进行对比，以此判断是否下载完成
                            if (downloadLen == length) break;
                        }
                        fileout.close();
                        if (downloadLen == receivefile.length()) {
                            System.out.println("文件传输成功，丢包率为0");
                        } else {
                            System.out.println("文件传输失败");
                        }
                        sb.append(dtf.format(time) + "----" + "用户：" + this.user.getName() + "上传文件：" + sm.getName());
                    } else {
                        onlineClient.remove(clientAddress);
                        isOnline = false;
                        sb.append(dtf.format(time) + "----" + "用户：" + this.user.getName() + "下线");
                        System.out.println("用户：" + this.user.getName() + "下线");
                    }
                }
                //对消息进行分发与持久化缓存到本地
                if (sb.toString().trim().length() > 0) {
                    forwardMessageOthers(sb.toString(), clientAddress);
                    writeTalkFileRepository(sb.toString() + "\r\n");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != print) {
                print.close();
            }
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 聊天记录缓存
     *
     * @param str
     */
    private synchronized void writeTalkFileRepository(String str) {
        File file = new File("");
        String projectPath = null;
        FileOutputStream fis = null;
        try {
            projectPath = file.getCanonicalPath();
            String diskPath = projectPath.substring(0, projectPath.indexOf("\\") + 1);
            File talkContentFile = new File(diskPath + "Repository\\talkcontent.txt");
            if (!talkContentFile.exists()) {
                talkContentFile.getParentFile().mkdirs();
            }
            fis = new FileOutputStream(talkContentFile, true);
            fis.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 分发消息
     *
     * @param message   消息内容
     * @param ipAddress 当前用户地址
     */
    private synchronized void forwardMessageOthers(String message, String ipAddress) throws IOException {
        Set<String> onineUsers = onlineClient.keySet();
        for (String user : onineUsers) {
            if (!user.equals(ipAddress)) {
                try {
                    Socket s = onlineClient.get(user);
                    PrintStream ps = new PrintStream(s.getOutputStream());
                    if (null != ps) {
                        SendMessage sm = new SendMessage();
                        sm.setMessageType(MessageType.TEXT);
                        sm.setTime(System.currentTimeMillis());
                        sm.setContent(message);
                        ps.println(JSON.toJSONString(sm));
                        ps.flush();
                    }
                } catch (SocketException e) {
                    onlineClient.remove(user);
                }
            }
        }
    }
}
